﻿
namespace System.Security.Cryptography
{
    /// <summary>
    /// Represents the abstract class from which all implementations of Hash-based
    /// Message Authentication Code (HMAC) must derive.
    /// </summary>
    public abstract class HMAC : KeyedHashAlgorithm
    {
        int blockSize = 0x40;
        internal HashAlgorithm hash1;
        internal HashAlgorithm hash2;
        bool hashing;
        internal string hashName;
        byte[] inner;
        byte[] outter;

        /// <summary>
        /// Gets or sets the block size to use in the hash value.
        /// </summary>
        protected int BlockSizeValue
        {
            get { return this.blockSize; }
            set { this.blockSize = value; }
        }

        /// <summary>
        /// Gets or sets the name of the hash algorithm to use for hashing.
        /// </summary>
        public string HashName
        {
            get { return this.hashName; }
            set
            {
                if (this.hashing)
                {
                    throw new CryptographicException("Cryptography_HashNameSet");
                }

                this.hashName = value;
                this.hash1 = HashAlgorithm.Create(this.hashName);
                this.hash2 = HashAlgorithm.Create(this.hashName);
            }
        }

        /// <summary>
        /// Gets or sets the key to use in the hash algorithm.
        /// </summary>
        public override byte[] Key
        {
            get { return (byte[])base.KeyValue.Clone(); }
            set
            {
                if (this.hashing)
                {
                    throw new CryptographicException("Cryptography_HashNameSet");
                }

                this.InitializeKey(value);
            }
        }

        /// <summary>
        /// Initializes a new instance of the System.Security.Cryptography.HMAC class.
        /// </summary>
        protected HMAC() { }

        /// <summary>
        /// When overridden in a derived class, routes data written to the object into
        /// the default System.Security.Cryptography.HMAC hash algorithm for computing
        /// the hash value.
        /// </summary>
        /// <param name="array">The input data.</param>
        /// <param name="ibStart">The offset into the byte array from which to begin using data.</param>
        /// <param name="cbSize">The number of bytes in the array to use as data.</param>
        protected override void HashCore(byte[] array, int ibStart, int cbSize)
        {
            if (!this.hashing)
            {
                this.hash1.TransformBlock(this.inner, 0, this.inner.Length, this.inner, 0);
                this.hashing = true;
            }

            this.hash1.TransformBlock(array, ibStart, cbSize, array, ibStart);
        }

        /// <summary>
        /// When overridden in a derived class, finalizes the hash computation after
        /// the last data is processed by the cryptographic stream object.
        /// </summary>
        /// <returns></returns>
        protected override byte[] HashFinal()
        {
            if (!this.hashing)
            {
                this.hash1.TransformBlock(this.inner, 0, this.inner.Length, this.inner, 0);
                this.hashing = true;
            }

            this.hash1.TransformFinalBlock(new byte[0], 0, 0);
            byte[] hashValue = this.hash1.Hash;

            this.hash2.TransformBlock(this.outter, 0, this.outter.Length, this.outter, 0);
            this.hash2.TransformBlock(hashValue, 0, hashValue.Length, hashValue, 0);

            this.hashing = false;
            this.hash2.TransformFinalBlock(new byte[0], 0, 0);

            return this.hash2.Hash;
        }

        /// <summary>
        /// Initializes an instance of the default implementation of System.Security.Cryptography.HMAC.
        /// </summary>
        public override void Initialize()
        {
            this.hash1.Initialize();
            this.hash2.Initialize();
            this.hashing = false;
        }

        /// <summary>
        /// Initialize the key.
        /// </summary>
        /// <param name="key">The key data.</param>
        internal void InitializeKey(byte[] key)
        {
            this.inner = null;
            this.outter = null;

            if (key.Length > this.blockSize)
            {
                base.KeyValue = this.hash1.ComputeHash(key);
            }
            else
            {
                base.KeyValue = (byte[])key.Clone();
            }

            this.UpdateIOPadBuffers();
        }

        private void UpdateIOPadBuffers()
        {
            int num;

            if (this.inner == null)
            {
                this.inner = new byte[this.blockSize];
            }

            if (this.outter == null)
            {
                this.outter = new byte[this.blockSize];
            }

            for (num = 0; num < this.blockSize; num++)
            {
                this.inner[num] = 0x36;
                this.outter[num] = 0x5c;
            }

            for (num = 0; num < base.KeyValue.Length; num++)
            {
                this.inner[num] = (byte)(this.inner[num] ^ base.KeyValue[num]);
                this.outter[num] = (byte)(this.outter[num] ^ base.KeyValue[num]);
            }

        }

        /// <summary>
        /// Releases the unmanaged resources used by the System.Security.Cryptography.HMAC
        /// class when a key change is legitimate and optionally releases the managed
        /// resources.
        /// </summary>
        /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (this.hash1 != null)
                {
                    this.hash1.Clear();
                }

                if (this.hash2 != null)
                {
                    this.hash2.Clear();
                }

                if (this.inner != null)
                {
                    Array.Clear(this.inner, 0, this.inner.Length);
                }

                if (this.outter != null)
                {
                    Array.Clear(this.outter, 0, this.outter.Length);
                }
            }

            base.Dispose(disposing);
        }
    }
}